#ifndef DISPLAY_H
#define	DISPLAY_H

#include "main.h"
#include "stm32f0xx_hal.h"
#include "spi.h"
#include "string.h"
#include "settings.c"
#include "font5x7.c"
#include "font3x5.c"
#include "bcode.c"

//config
//#define DISPLAY_ROT NOROT //set to NOROT, ROTCCW, ROT180 or ROTCW. Can be used to play in portrait mode.
//#define ENABLE_BITMAPS 1 //will replace bitmaps with rectangles if disabled
//#define ENABLE_GRAYSCALE 1 //allows the use of the GRAY color
//#define EXTENDED_NOTE_RANGE 0 //allows the use of notes above A 5... please avoid that they sound really bad

#define LCD_BY_ON()   HAL_GPIO_WritePin(LCDBY_GPIO_Port,LCDBY_Pin,GPIO_PIN_SET);
#define LCD_BY_OFF()  HAL_GPIO_WritePin(LCDBY_GPIO_Port,LCDBY_Pin,GPIO_PIN_RESET);
#define LCD_BB_ON()   HAL_GPIO_WritePin(LCDBB_GPIO_Port,LCDBB_Pin,GPIO_PIN_SET);
#define LCD_BB_OFF()  HAL_GPIO_WritePin(LCDBB_GPIO_Port,LCDBB_Pin,GPIO_PIN_RESET);
#define BACK_LIGHT_NONE         0X00
#define BACK_LIGHT_YELLOW       0X01
#define BACK_LIGHT_BLUE         0X02


#define _BV(bit) (1 << (bit))
#define abs(x)    ((x)>0?(x):-(x))
#define pgm_read_byte(x)  (uint8_t)*(x)
#define pgm_read_word(x)  (uint8_t)*(x)
#define swap(a, b) { int8_t t = a; a = b; b = t; }
#define max(a,b) (a>b)?a:b
#define min(a,b) (a<b)?a:b
#define F(s) (uint8_t *)(s)


//colors
#define WHITE 0
#define BLACK 1
#define INVERT 2
#define GRAY 3

//for extended bitmap function :
#define NOROT 0
#define ROTCCW 1
#define ROT180 2
#define ROTCW 3
#define NOFLIP 0
#define FLIPH 1
#define FLIPV 2
#define FLIPVH 3

#if (DISPLAY_ROT == NO_ROT)||(DISPLAY_ROT == ROT180) //landscape mode
#define LCDWIDTH 84
#define LCDHEIGHT 48
#else //portrait mode
#define LCDWIDTH 48
#define LCDHEIGHT 84
#endif
#define LCDHEIGHT_NOROT 48
#define LCDWIDTH_NOROT 84

#define swap(a, b) { int8_t t = a; a = b; b = t; }

#define PCD8544_POWERDOWN 0x04
#define PCD8544_ENTRYMODE 0x02
#define PCD8544_EXTENDEDINSTRUCTION 0x01

#define PCD8544_DISPLAYBLANK 0x0
#define PCD8544_DISPLAYNORMAL 0x4
#define PCD8544_DISPLAYALLON 0x1
#define PCD8544_DISPLAYINVERTED 0x5

// H = 0
#define PCD8544_FUNCTIONSET 0x20
#define PCD8544_DISPLAYCONTROL 0x08
#define PCD8544_SETYADDR 0x40
#define PCD8544_SETXADDR 0x80
#define PCD8544_SETXADDR 0x80

// H = 1
#define PCD8544_SETTEMP 0x04
#define PCD8544_SETBIAS 0x10
#define PCD8544_SETVOP 0x80

extern uint8_t _displayBuffer[];
extern const uint8_t font3x3[];
extern const uint8_t font3x5[];
extern const uint8_t font5x7[];


class Display{
public:
	void begin();

	void command(uint8_t c);
	void data(uint8_t c);
	uint8_t* getBuffer();

	void setContrast(uint8_t val);
	void clear(void);
	void update();

	void setColor(int8_t c);
	void setColor(int8_t c, int8_t bg);
	void setCursor(uint8_t x,uint8_t y);
	inline void drawPixel(int8_t x, int8_t y);
	inline uint8_t getPixel(int8_t x, int8_t y);

	void drawLine(int8_t x0, int8_t y0, int8_t x1, int8_t y1);
	void drawFastVLine(int8_t x, int8_t y, int8_t h);
	void drawFastHLine(int8_t x, int8_t y, int8_t w);
	void drawRect(int8_t x, int8_t y, int8_t w, int8_t h);
	void fillRect(int8_t x, int8_t y, int8_t w, int8_t h);
	void fillScreen(uint8_t color);

	void drawCircle(int8_t x0, int8_t y0, int8_t r);
	void drawCircleHelper(int8_t x0, int8_t y0, int8_t r, uint8_t cornername);
	void fillCircle(int8_t x0, int8_t y0, int8_t r);
	void fillCircleHelper(int8_t x0, int8_t y0, int8_t r, uint8_t cornername, int8_t delta);

	void drawTriangle(int8_t x0, int8_t y0, int8_t x1, int8_t y1, int8_t x2, int8_t y2);
	void fillTriangle(int8_t x0, int8_t y0, int8_t x1, int8_t y1, int8_t x2, int8_t y2);
	void drawRoundRect(int8_t x0, int8_t y0, int8_t w, int8_t h, int8_t radius);
	void fillRoundRect(int8_t x0, int8_t y0, int8_t w, int8_t h, int8_t radius);
	
	void drawBitmap(int8_t x, int8_t y, const uint8_t *bitmap);
	void drawBitmap(int8_t x, int8_t y, const uint8_t *bitmap, uint8_t rotation, uint8_t flip);
	bool getBitmapPixel(const uint8_t* bitmap, uint8_t x, uint8_t y);
	
	void setFont(const uint8_t* f);
	uint8_t fontWidth, fontHeight;
	void drawChar(int8_t x, int8_t y, unsigned char c, uint8_t size);
	
	size_t write(uint8_t);
	size_t print(const uint8_t *dat);
	size_t print(long n);
	size_t println(const uint8_t *dat);
	size_t println(long n);
	size_t printNumber(unsigned long n, uint8_t base);	
	
	void setBackLight(uint8_t);
	
	bool persistence; //disable clean() at each frame if true
	bool textWrap; // If set, 'wrap' text at right edge of 
	uint8_t fontSize;
	int8_t cursorX, cursorY;
	uint8_t contrast;
	uint8_t frameCount;

private:	
	uint8_t *font;
	uint8_t color, bgcolor;
};

inline uint8_t* Display::getBuffer(){
	return _displayBuffer;
}

inline void Display::drawPixel(int8_t x, int8_t y) {
	if ((x < 0) || (x >= LCDWIDTH) || (y < 0) || (y >= LCDHEIGHT))
	return;
	
	uint8_t c = color;
	if(color == INVERT){
	 c = !getPixel(x, y);
	} else if(color == GRAY){
		if(((frameCount & 0x01) ^ (((x & 0x01) ^ (y & 0x01)) == 0))){ //alternative checkers pattern
			c = WHITE;
		} else {
			c= BLACK;
		}
	}
	
	if(c == WHITE){ //white
#if DISPLAY_ROT == NOROT
		_displayBuffer[x + (y / 8) * LCDWIDTH_NOROT] &= ~_BV(y % 8);
#elif DISPLAY_ROT == ROTCCW
		_displayBuffer[LCDHEIGHT - y - 1 + (x / 8) * LCDWIDTH_NOROT] &= ~_BV(x % 8);
#elif DISPLAY_ROT == ROT180
		_displayBuffer[LCDWIDTH - x - 1 + ((LCDHEIGHT - y - 1) / 8) * LCDWIDTH_NOROT] &= ~_BV((LCDHEIGHT - y - 1) % 8);
#elif DISPLAY_ROT == ROTCW
		_displayBuffer[y + ((LCDWIDTH - x - 1) / 8) * LCDWIDTH_NOROT] &= ~_BV((LCDWIDTH - x - 1) % 8);
#endif
		return;
	}
	else { //black
#if DISPLAY_ROT == NOROT
		_displayBuffer[x + (y / 8) * LCDWIDTH_NOROT] |= _BV(y % 8);
#elif DISPLAY_ROT == ROTCCW
		_displayBuffer[LCDHEIGHT - y - 1 + (x / 8) * LCDWIDTH_NOROT] |= _BV(x % 8);
#elif DISPLAY_ROT == ROT180
		_displayBuffer[LCDWIDTH - x - 1 + ((LCDHEIGHT - y - 1) / 8) * LCDWIDTH_NOROT] |= _BV((LCDHEIGHT - y - 1) % 8);
#elif DISPLAY_ROT == ROTCW
		_displayBuffer[y + ((LCDWIDTH - x - 1) / 8) * LCDWIDTH_NOROT] |= _BV((LCDWIDTH - x -1) % 8);
#endif
		return;
	}
}

inline uint8_t Display::getPixel(int8_t x, int8_t y) {
	if ((x < 0) || (x >= LCDWIDTH) || (y < 0) || (y >= LCDHEIGHT))
	return 0;

	return (_displayBuffer[x + (y / 8) * LCDWIDTH] >> (y % 8)) & 0x1;
}


#endif	/* DISPLAY_H */

